<template>
	<transition name="viewer-fade">
		<div tabindex="-1" ref="el-image-viewer__wrapper" class="el-image-viewer__wrapper" :style="{ 'z-index': viewerZIndex }">
			<div class="el-image-viewer__mask" @click.self="handleMaskClick"></div>
			<!-- CLOSE -->
			<span class="el-image-viewer__btn el-image-viewer__close" @click="hide">
				<i class="el-icon-close"></i>
			</span>
			<!-- ARROW -->
			<template v-if="!isSingle">
				<span class="el-image-viewer__btn el-image-viewer__prev" :class="{ 'is-disabled': !infinite && isFirst }" @click="prev">
					<i class="el-icon-arrow-left" />
				</span>
				<span class="el-image-viewer__btn el-image-viewer__next" :class="{ 'is-disabled': !infinite && isLast }" @click="next">
					<i class="el-icon-arrow-right" />
				</span>
			</template>
			<!-- ACTIONS -->
			<div class="el-image-viewer__btn el-image-viewer__actions">
				<div class="el-image-viewer__actions__inner">
					<i class="el-icon-zoom-out" @click="handleActions('zoomOut')"></i>
					<i class="el-icon-zoom-in" @click="handleActions('zoomIn')"></i>
					<i class="el-image-viewer__actions__divider"></i>
					<i :class="mode.icon" @click="toggleMode"></i>
					<i class="el-image-viewer__actions__divider"></i>
					<i class="el-icon-refresh-left" @click="handleActions('anticlocelise')"></i>
					<i class="el-icon-refresh-right" @click="handleActions('clocelise')"></i>
				</div>
			</div>
			<!-- CANVAS -->
			<div class="el-image-viewer__canvas">
				<img v-for="(url, i) in urlList" v-if="i === index" ref="img" class="el-image-viewer__img" :key="url" :src="currentImg" :style="imgStyle"
					referrerpolicy='no-referrer' @load="handleImgLoad" @error="handleImgError" @mousedown="handleMouseDown">
			</div>
		</div>
	</transition>
</template>

<script>
import { on, off } from 'element-ui/src/utils/dom';
import { rafThrottle, isFirefox } from 'element-ui/src/utils/util';
import { PopupManager } from 'element-ui/src/utils/popup';

const Mode = {
	CONTAIN: {
		name: 'contain',
		icon: 'el-icon-full-screen'
	},
	ORIGINAL: {
		name: 'original',
		icon: 'el-icon-c-scale-to-original'
	}
};

const mousewheelEventName = isFirefox() ? 'DOMMouseScroll' : 'mousewheel';

export default {
	name: 'elImageViewer',

	props: {
		urlList: {
			type: Array,
			default: () => []
		},
		zIndex: {
			type: Number,
			default: 2000
		},
		onSwitch: {
			type: Function,
			default: () => {}
		},
		onClose: {
			type: Function,
			default: () => {}
		},
		initialIndex: {
			type: Number,
			default: 0
		},
		appendToBody: {
			type: Boolean,
			default: true
		},
		maskClosable: {
			type: Boolean,
			default: true
		}
	},

	data() {
		return {
			index: this.initialIndex,
			isShow: false,
			infinite: true,
			loading: false,
			mode: Mode.CONTAIN,
			transform: { scale: 1, deg: 0, offsetX: 0, offsetY: 0, enableTransition: false }
		};
	},
	computed: {
		isSingle() {
			return this.urlList.length <= 1;
		},
		isFirst() {
			return this.index === 0;
		},
		isLast() {
			return this.index === this.urlList.length - 1;
		},
		currentImg() {
			return this.urlList[this.index];
		},
		imgStyle() {
			const { scale, deg, offsetX, offsetY, enableTransition } = this.transform;
			const style = {
				transform: `scale(${scale}) rotate(${deg}deg)`,
				transition: enableTransition ? 'transform .3s' : '',
				'margin-left': `${offsetX}px`,
				'margin-top': `${offsetY}px`
			};
			if(this.mode === Mode.CONTAIN) {
				style.maxWidth = style.maxHeight = '100%';
			}
			return style;
		},
		viewerZIndex() {
			const nextZIndex = PopupManager.nextZIndex();
			return this.zIndex > nextZIndex ? this.zIndex : nextZIndex;
		}
	},
	watch: {
		index: {
			handler: function (val) {
				this.reset();
				this.onSwitch(val);
			}
		},
		currentImg(val) {
			this.$nextTick(_ => {
				const $img = this.$refs.img[0];
				if(!$img.complete) {
					this.loading = true;
				}
			});
		}
	},
	methods: {
		hide() {
			this.deviceSupportUninstall();
			this.onClose();
		},
		deviceSupportInstall() {
			this._keyDownHandler = e => {
				e.stopPropagation();
				const keyCode = e.keyCode;
				switch(keyCode) {
					// ESC
				case 27:
					this.hide();
					break;
					// SPACE
				case 32:
					this.toggleMode();
					break;
					// LEFT_ARROW
				case 37:
					this.prev();
					break;
					// UP_ARROW
				case 38:
					this.handleActions('zoomIn');
					break;
					// RIGHT_ARROW
				case 39:
					this.next();
					break;
					// DOWN_ARROW
				case 40:
					this.handleActions('zoomOut');
					break;
				}
			};
			this._mouseWheelHandler = rafThrottle(e => {
				const delta = e.wheelDelta ? e.wheelDelta : -e.detail;
				if(delta > 0) {
					this.handleActions('zoomIn', {
						zoomRate: 0.015,
						enableTransition: false
					});
				} else {
					this.handleActions('zoomOut', {
						zoomRate: 0.015,
						enableTransition: false
					});
				}
			});
			on(document, 'keydown', this._keyDownHandler);
			if(this.$refs['el-image-viewer__wrapper']) {
				on(this.$refs['el-image-viewer__wrapper'], mousewheelEventName, this._mouseWheelHandler);
			}
		},
		deviceSupportUninstall() {
			off(document, 'keydown', this._keyDownHandler);
			off(document, mousewheelEventName, this._mouseWheelHandler);
			off(this.$refs['el-image-viewer__wrapper'], mousewheelEventName, this._mouseWheelHandler);
			this._keyDownHandler = null;
			this._mouseWheelHandler = null;
		},
		handleImgLoad(e) {
			this.loading = false;
		},
		handleImgError(e) {
			this.loading = false;
			e.target.alt = '加载失败';
		},
		handleMouseDown(e) {
			if(this.loading || e.button !== 0) return;

			const { offsetX, offsetY } = this.transform;
			const startX = e.pageX;
			const startY = e.pageY;
			this._dragHandler = rafThrottle(ev => {
				this.transform.offsetX = offsetX + ev.pageX - startX;
				this.transform.offsetY = offsetY + ev.pageY - startY;
			});
			on(document, 'mousemove', this._dragHandler);
			on(document, 'mouseup', ev => {
				off(document, 'mousemove', this._dragHandler);
			});

			e.preventDefault();
		},
		handleMaskClick() {
			if(this.maskClosable) {
				this.hide();
			}
		},
		reset() {
			this.transform = {
				scale: 1,
				deg: 0,
				offsetX: 0,
				offsetY: 0,
				enableTransition: false
			};
		},
		toggleMode() {
			if(this.loading) return;

			const modeNames = Object.keys(Mode);
			const modeValues = Object.values(Mode);
			const index = modeValues.indexOf(this.mode);
			const nextIndex = (index + 1) % modeNames.length;
			this.mode = Mode[modeNames[nextIndex]];
			this.reset();
		},
		prev() {
			if(this.isFirst && !this.infinite) return;
			const len = this.urlList.length;
			this.index = (this.index - 1 + len) % len;
		},
		next() {
			if(this.isLast && !this.infinite) return;
			const len = this.urlList.length;
			this.index = (this.index + 1) % len;
		},
		handleActions(action, options = {}) {
			if(this.loading) return;
			const { zoomRate, rotateDeg, enableTransition } = {
				zoomRate: 0.2,
				rotateDeg: 90,
				enableTransition: true,
				...options
			};
			const { transform } = this;
			switch(action) {
			case 'zoomOut':
				if(transform.scale > 0.2) {
					transform.scale = parseFloat((transform.scale - zoomRate).toFixed(3));
				}
				break;
			case 'zoomIn':
				transform.scale = parseFloat((transform.scale + zoomRate).toFixed(3));
				break;
			case 'clocelise':
				transform.deg += rotateDeg;
				break;
			case 'anticlocelise':
				transform.deg -= rotateDeg;
				break;
			}
			transform.enableTransition = enableTransition;
		}
	},
	mounted() {
		this.deviceSupportInstall();
		if(this.appendToBody) {
			document.body.appendChild(this.$el);
		}
		// add tabindex then wrapper can be focusable via Javascript
		// focus wrapper so arrow key can't cause inner scroll behavior underneath
		this.$refs['el-image-viewer__wrapper'].focus();
	},
	destroyed() {
		// if appendToBody is true, remove DOM node after destroy
		if(this.appendToBody && this.$el && this.$el.parentNode) {
			this.$el.parentNode.removeChild(this.$el);
		}
	}
};
</script>

<style scoped>
.el-image-viewer__wrapper {
	position: absolute;
}

.el-image-viewer__mask {
	background: inherit;
}

.el-image-viewer__close {
	display: none;
}

.el-image-viewer__canvas {
	overflow: hidden;
	position: relative;
}

.el-image-viewer__img {
	position: absolute;
}
</style>